import java.util.concurrent.Semaphore;
import java.util.function.IntConsumer;

/**
 * 1195. 交替打印字符串
 * https://leetcode-cn.com/problems/fizz-buzz-multithreaded/
 */
public class Solutions_1195 {
    public static void main(String[] args) {
        int n = 15;

        FizzBuzz obj = new FizzBuzz(n);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    obj.fizz(new Runnable() {
                        @Override
                        public void run() {
                            System.out.print("fizz ");
                        }
                    });
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    obj.buzz(new Runnable() {
                        @Override
                        public void run() {
                            System.out.print("buzz ");
                        }
                    });
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    obj.fizzbuzz(new Runnable() {
                        @Override
                        public void run() {
                            System.out.print("fizzbuzz ");
                        }
                    });
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    obj.number(System.out::print);
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t4.start();
        t3.start();
        t2.start();
        t1.start();
    }
}

/**
 * 解题思路：信号量
 * 主从进程：number 为主进程（控制信号量），其他 3 个进程为从进程
 */
class FizzBuzz {
    private int n;
    private Semaphore fizz, buzz, fizzbuzz, number;
    private int cur;

    public FizzBuzz(int n) {
        this.n = n;
        fizz = new Semaphore(0);
        buzz = new Semaphore(0);
        fizzbuzz = new Semaphore(0);
        number = new Semaphore(0);
        cur = 1;
    }

    // printFizz.run() outputs "fizz".
    public void fizz(Runnable printFizz) throws InterruptedException {
        while (true) {
            fizz.acquire();
            if (cur > n) {
                // 线程执行完成
                break;
            }
            printFizz.run();
            number.release();
        }
    }

    // printBuzz.run() outputs "buzz".
    public void buzz(Runnable printBuzz) throws InterruptedException {
        while (true) {
            buzz.acquire();
            if (cur > n) {
                // 线程执行完成
                break;
            }
            printBuzz.run();
            number.release();
        }
    }

    // printFizzBuzz.run() outputs "fizzbuzz".
    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
        while (true) {
            fizzbuzz.acquire();
            if (cur > n) {
                // 线程执行完成
                break;
            }
            printFizzBuzz.run();
            number.release();
        }
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void number(IntConsumer printNumber) throws InterruptedException {
        while (cur <= n) {
            if ((cur % 3 == 0) && (cur % 5 == 0)) {
                // 释放信号量，让线程 3 执行
                fizzbuzz.release();
            } else if (cur % 5 == 0) {
                // 释放信号量，让线程 2 执行
                buzz.release();
            } else if (cur % 3 == 0) {
                // 释放信号量，让线程 1 执行
                fizz.release();
            } else {
                printNumber.accept(cur);
                System.out.print(" ");
                number.release();
            }
            number.acquire();
            cur ++;
        }
        // 防止程序未结束
        fizz.release();
        buzz.release();
        fizzbuzz.release();
    }
}
